package org.freeocs.commons.search;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.commons.beanutils.NestedNullException;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;

/**
 * 搜索代理
 * 
 * <pre>
 * 碧水长天兮，昭昭日月不同弦
 * 知向谁边兮，点点渔火不同眠
 * 青山如黛兮，悠悠吴钩共秦剑
 * 孤舟一叶兮，化做了淡梦寒烟
 * </pre>
 * 
 * @author chirs
 */
@SuppressWarnings( { "unchecked", "unused" })
public class SearchProxy {

	private final static Log log = LogFactory.getLog(SearchProxy.class);

	public final static int MAX_RESULT_COUNT = 200;

	// 用于存储对象的类名
	private final static String CLASSNAME_FIELD = "___className";

	private static String _baseIndexPath;

	public static void init(String _base_index_path) {
		_baseIndexPath = _base_index_path;
		if (!_baseIndexPath.endsWith(File.separator))
			_baseIndexPath += File.separator;
	}

	/**
	 * 添加文档
	 * 
	 * @param doc
	 * @throws Exception
	 */
	public static synchronized void add(SearchEnabled doc) throws Exception {
		if (doc == null)
			return;

		Document lucene_doc = new Document();

		// Set keyword field
		String key = getField(doc, doc.getKeywordField());
		lucene_doc.add(Keyword(doc.getKeywordField(), key));

		// Set identity(classname) of object
		lucene_doc.add(Keyword(CLASSNAME_FIELD, doc.getClass().getName()));

		// Set storage field
		String[] storeFields = doc.getStoreFields();
		for (int i = 0; storeFields != null && i < storeFields.length; i++) {
			String propertyValue = getField(doc, storeFields[i]);
			if (propertyValue != null)
				lucene_doc.add(Keyword(storeFields[i], propertyValue));
		}
		// Set indexed field
		String[] indexFields = doc.getIndexFields();
		for (int i = 0; indexFields != null && i < indexFields.length; i++) {
			String propertyValue = getField(doc, indexFields[i]);
			lucene_doc.add(UnStored(indexFields[i], propertyValue));
		}
		// Write document
		IndexWriter writer = getWriter(doc.name());
		try {
			writer.addDocument(lucene_doc);
			writer.optimize();
		} finally {
			try {
				writer.close();
			} catch (Exception e) {
				log.error("Error occur when closing IndexWriter", e);
			} finally {
				writer = null;
			}
			lucene_doc = null;
		}

	}

	/**
	 * 从索引中删除文档
	 * 
	 * @param doc
	 * @return
	 * @throws Exception
	 */
	public static synchronized int remove(SearchEnabled doc) {
		if (doc == null)
			return -1;

		IndexReader reader = null;
		try {
			reader = getReader(doc.name());
			String pvalue = getField(doc, doc.getKeywordField());
			Term keyTerm = new Term(doc.getKeywordField(), pvalue);
			return reader.deleteDocuments(keyTerm);
		} catch (Exception e) {
			log.error("Error where delete index of " + doc, e);
		} finally {
			if (reader != null)
				try {
					reader.close();
				} catch (Exception e) {
					log.error("Error occur when closing IndexReader", e);
				} finally {
					reader = null;
				}
		}
		return -1;
	}

	/**
	 * 更新文档的索引
	 * 
	 * @param doc
	 * @return
	 * @throws Exception
	 */
	public static void update(SearchEnabled doc) throws Exception {
		if (doc == null)
			return;
		remove(doc);
		add(doc);
	}

	/**
	 * 文档搜索
	 * 
	 * @param params
	 * @return
	 * @throws Exception
	 */
	public static List search(SearchParameter params) throws Exception {
		if (params == null)
			return null;

		SearchEnabled searching = (SearchEnabled) params.getSearchObject().newInstance();

		StringBuffer path = new StringBuffer(_baseIndexPath);
		path.append(searching.name());
		File f = new File(path.toString());
		if (!f.exists())
			return null;

		// IndexSearcher searcher = new IndexSearcher(path.toString());

		// 设置搜索关键字
		BooleanQuery comboQuery = new BooleanQuery();
		int _query_count = 0;
		StringTokenizer st = new StringTokenizer(params.getSearchKey());
		while (st.hasMoreElements()) {
			String q = st.nextToken();
			String[] indexFields = searching.getIndexFields();
			for (int i = 0; i < indexFields.length; i++) {
				QueryParser qp = new QueryParser(indexFields[i], analyzer);
				try {
					Query subjectQuery = qp.parse(q);
					comboQuery.add(subjectQuery, BooleanClause.Occur.SHOULD);
					_query_count++;
				} catch (Exception e) {
					log.error("Add query parameter failed. key=" + q, e);
				}
			}
		}

		if (_query_count == 0)// 没有任何关键字条件
			return null;

		// 搜索的附加条件
		MultiFilter multiFilter = null;
		HashMap conds = params.getConditions();
		if (conds != null) {
			Iterator keys = conds.keySet().iterator();
			while (keys.hasNext()) {
				if (multiFilter == null)
					multiFilter = new MultiFilter(0);
				String key = (String) keys.next();
				multiFilter.add(new FieldFilter(key, conds.get(key).toString()));
			}
		}

		/*
		 * Creates a sort, possibly in reverse, by terms in the given field with
		 * the type of term values explicitly given.
		 */
		SortField[] s_fields = new SortField[2];
		s_fields[0] = SortField.FIELD_SCORE;
		s_fields[1] = new SortField(searching.getKeywordField(), SortField.INT, true);
		Sort sort = new Sort(s_fields);

		List results = new ArrayList(1);

		return results;
	}

	/**
	 * 获取嵌套属性的类型
	 * 
	 * @param obj
	 * @param field
	 * @return
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IntrospectionException
	 */
	private static Class getNestedPropertyType(Object obj, String field) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException,
			NoSuchFieldException, IntrospectionException {
		StringTokenizer st = new StringTokenizer(field, ".");
		Class nodeClass = obj.getClass();
		while (st.hasMoreElements()) {
			String f = st.nextToken();
			PropertyDescriptor[] props = Introspector.getBeanInfo(nodeClass).getPropertyDescriptors();
			for (int i = 0; i < props.length; i++) {
				if (props[i].getName().equals(f)) {
					nodeClass = props[i].getPropertyType();
					continue;
				}
			}
		}
		return nodeClass;
	}

	/**
	 * 设置字段值
	 * 
	 * @param obj
	 * @param field
	 * @param value
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws IntrospectionException
	 * @throws InstantiationException
	 */
	private static void setNestedProperty(Object obj, String field, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException,
			IntrospectionException, InstantiationException {
		String[] fields = StringUtils.split(field, '.');
		if (fields.length > 1) {
			Class nodeClass = obj.getClass();
			StringBuffer tmp_prop = new StringBuffer();
			for (int fi = 0; fi < (fields.length - 1); fi++) {
				String f = fields[fi];
				if (tmp_prop.length() > 0)
					tmp_prop.append('.');
				tmp_prop.append(f);
				PropertyDescriptor[] props = Introspector.getBeanInfo(nodeClass).getPropertyDescriptors();
				for (int i = 0; i < props.length; i++) {
					if (props[i].getName().equals(f)) {
						if (PropertyUtils.getNestedProperty(obj, tmp_prop.toString()) == null) {
							nodeClass = props[i].getPropertyType();
							PropertyUtils.setNestedProperty(obj, tmp_prop.toString(), nodeClass.newInstance());
						}
						continue;
					}
				}
			}
		}
		PropertyUtils.setNestedProperty(obj, field, value);
	}

	/**
	 * 获取索引读
	 * 
	 * @param name
	 * @return
	 * @throws IOException
	 */
	private static IndexReader getReader(String name) throws IOException {
		StringBuffer path = new StringBuffer(_baseIndexPath);
		path.append(name);
		try {
			return IndexReader.open(path.toString());
		} finally {
			path = null;
		}
	}

	/**
	 * 获取索引写
	 * 
	 * @param name
	 * @return
	 * @throws IOException
	 */
	@SuppressWarnings("deprecation")
	private static IndexWriter getWriter(String name) throws IOException {
		StringBuffer path = new StringBuffer(_baseIndexPath);
		path.append(name);
		String index_path = path.toString();
		File rp = new File(index_path);
		if (!rp.exists())
			rp.mkdirs();
		int wc = 0;
		// waiting for the lock of indexes
		while (wc < 10 && IndexWriter.isLocked(index_path)) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				return null;
			}
			wc++;
		}
		path.append(File.separator);
		path.append(SEGMENTS);
		File segments = new File(path.toString());
		try {
			boolean bCreate = !segments.exists();
			return new IndexWriter(index_path, new StandardAnalyzer(), bCreate);
		} finally {
			path = null;
			segments = null;
			rp = null;
		}
	}

	private final static String SEGMENTS = "segments";

	/**
	 * 访问对象某个属性的值
	 * 
	 * @param obj
	 * @param field
	 * @return
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	private static String getField(Object obj, String field) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		try {
			Object fieldValue = PropertyUtils.getNestedProperty(obj, field);
			if (fieldValue instanceof String)
				return (String) fieldValue;
			if (fieldValue instanceof Date)
				return Long.toString(((Date) fieldValue).getTime());
			return String.valueOf(fieldValue);
		} catch (NestedNullException e) {
		}
		return null;
	}

	private static StandardAnalyzer analyzer = new StandardAnalyzer();

	protected static final Field Keyword(String name, String value) {
		return new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED);
	}

	protected static final Field Text(String name, String value) {
		return new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED);
	}

	protected static final Field UnStored(String name, String value) {
		return new Field(name, value, Field.Store.NO, Field.Index.NOT_ANALYZED);
	}
}